// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 服务器的实现

package httptest

import (
	"crypto/tls"
	"crypto/x509"
	"flag"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/http/internal/testcert"
	"os"
	"strings"
	"sync"
	"time"
)

// 服务器是一个HTTP服务器，监听
// 本地环回接口上系统选择的端口，用于端到端HTTP测试。
type Server struct {
	URL      string // 格式为http:
	Listener net.Listener

	// EnableHTTP2的基本URL控制是否在服务器上启用http/2 
	// 。必须在调用
	// NewUnstartedServer和调用Server.StartTLS之间设置。
	EnableHTTP2 bool

	// TLS是可选的TLS配置，在TLS启动后使用新配置
	// 填充。如果在调用StartTLS 
	// 之前在未启动的服务器上设置，则现有字段将复制到新配置中。
	TLS *tls.Config

	// 在Start或StartTLS之前调用NewUnstartedServer和
	// 后，可能会更改配置。
	Config *http.Server

	// 证书是TLS配置证书的解析版本（如果存在）。
	certificate *x509.Certificate

	// wg统计此服务器上未完成的HTTP请求数。
	// 关闭块，直到所有请求完成。
	wg sync.WaitGroup

	mu     sync.Mutex // 防护关闭并连接
	closed bool
	conns  map[net.Conn]http.ConnState // 终端状态除外

	// 客户端配置为与服务器一起使用。
	// 调用Close时，其传输将自动关闭。
	client *http.Client
}

func newLocalListener() net.Listener {
	if serveFlag != "" {
		l, err := net.Listen("tcp", serveFlag)
		if err != nil {
			panic(fmt.Sprintf("httptest: failed to listen on %v: %v", serveFlag, err))
		}
		return l
	}
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
			panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err))
		}
	}
	return l
}

// 调试特定的基于http服务器的测试时，
// 此标志允许您运行
// go test-run=BrokenTest-httptest.server=127.0.0.1:8000 
// 启动损坏的服务器，以便手动与之交互。
// 我们只在调用方似乎知道它并且试图使用它时注册此标志，因为我们不想污染标志，而此
// 实际上不是我们API的一部分。别指望这个。
var serveFlag string

func init() {
	if strSliceContainsPrefix(os.Args, "-httptest.serve=") || strSliceContainsPrefix(os.Args, "--httptest.serve=") {
		flag.StringVar(&serveFlag, "httptest.serve", "", "if non-empty, httptest.NewServer serves on this address and blocks.")
	}
}

func strSliceContainsPrefix(v []string, pre string) bool {
	for _, s := range v {
		if strings.HasPrefix(s, pre) {
			return true
		}
	}
	return false
}

// NewServer启动并返回一个新服务器。
// 调用方应在完成后调用Close，以关闭它。
func NewServer(handler http.Handler) *Server {
	ts := NewUnstartedServer(handler)
	ts.Start()
	return ts
}

// NewUnstartedServer返回一个新服务器，但不启动它。
// 
// 更改其配置后，调用方应调用Start或
// /StartTLS。
// 
// 调用方应在完成后调用Close，将其关闭。
func NewUnstartedServer(handler http.Handler) *Server {
	return &Server{
		Listener: newLocalListener(),
		Config:   &http.Server{Handler: handler},
	}
}

// Start从新启动的服务器启动服务器。
func (s *Server) Start() {
	if s.URL != "" {
		panic("Server already started")
	}
	if s.client == nil {
		s.client = &http.Client{Transport: &http.Transport{}}
	}
	s.URL = "http:// “+s.Listener.Addr（）.String（）
	s.wrap()
	s.goServe()
	if serveFlag != "" {
		fmt.Fprintln(os.Stderr, "httptest: serving on", s.URL)
		select {}
	}
}

// /StartTLS从NewUnstartedServer在服务器上启动TLS。
func (s *Server) StartTLS() {
	if s.URL != "" {
		panic("Server already started")
	}
	if s.client == nil {
		s.client = &http.Client{Transport: &http.Transport{}}
	}
	cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)
	if err != nil {
		panic(fmt.Sprintf("httptest: NewTLSServer: %v", err))
	}

	existingConfig := s.TLS
	if existingConfig != nil {
		s.TLS = existingConfig.Clone()
	} else {
		s.TLS = new(tls.Config)
	}
	if s.TLS.NextProtos == nil {
		nextProtos := []string{"http/1.1"}
		if s.EnableHTTP2 {
			nextProtos = []string{"h2"}
		}
		s.TLS.NextProtos = nextProtos
	}
	if len(s.TLS.Certificates) == 0 {
		s.TLS.Certificates = []tls.Certificate{cert}
	}
	s.certificate, err = x509.ParseCertificate(s.TLS.Certificates[0].Certificate[0])
	if err != nil {
		panic(fmt.Sprintf("httptest: NewTLSServer: %v", err))
	}
	certpool := x509.NewCertPool()
	certpool.AddCert(s.certificate)
	s.client.Transport = &http.Transport{
		TLSClientConfig: &tls.Config{
			RootCAs: certpool,
		},
		ForceAttemptHTTP2: s.EnableHTTP2,
	}
	s.Listener = tls.NewListener(s.Listener, s.TLS)
	s.URL = "https:// “+s.Listener.Addr（）.String（）
	s.wrap()
	s.goServe()
}

// /NewTLServer使用TLS启动并返回新服务器。
// 调用方应在完成后调用Close，以关闭它。
func NewTLSServer(handler http.Handler) *Server {
	ts := NewUnstartedServer(handler)
	ts.StartTLS()
	return ts
}

type closeIdleTransport interface {
	CloseIdleConnections()
}

// 关闭关闭服务器并阻塞，直到此服务器上所有未完成的
// 请求完成。
func (s *Server) Close() {
	s.mu.Lock()
	if !s.closed {
		s.closed = true
		s.Listener.Close()
		s.Config.SetKeepAlivesEnabled(false)
		for c, st := range s.conns {
			// 强制关闭所有空闲连接（
			// 请求之间的连接）和新连接（连接
			// 但从未发送请求的连接）。StateNew连接是非常罕见的，只有在http客户端
			// 以前的不稳定测试中）才会看到。因此，在StateNew中存在一个连接的连接，没有关联的请求。我们只关闭StateIdle和
			// 套接字延迟绑定竞争
			// 拨号完成前拨打此服务器并获得空闲
			// 连接的情况下（在
			// StateNew，因为他们什么都没做。这是
			// 可能的StateNew将在几毫秒内执行某些操作，但在
			// 几毫秒内再次检查之前的CL不受欢迎（早期版本的
			// https:
			// 强制关闭StateNew。服务器的文档。关闭表示
			// 我们等待“未完成的请求”，所以我们不关闭任何东西
			// in StateActive.
			if st == http.StateIdle || st == http.StateNew {
				s.closeConn(c)
			}
		}
		// 如果此服务器未在5秒钟内关闭，请告诉用户原因。
		t := time.AfterFunc(5*time.Second, s.logCloseHangDebugInfo)
		defer t.Stop()
	}
	s.mu.Unlock()

	// 不属于httptest。服务器的正确性，但假设大多数httptest的用户
	// 服务器将使用标准的
	// 传输，请提供帮助退出并关闭所有空闲连接。
	if t, ok := http.DefaultTransport.(closeIdleTransport); ok {
		t.CloseIdleConnections()
	}

	// 同时关闭客户端空闲连接。
	if s.client != nil {
		if t, ok := s.client.Transport.(closeIdleTransport); ok {
			t.CloseIdleConnections()
		}
	}

	s.wg.Wait()
}

func (s *Server) logCloseHangDebugInfo() {
	s.mu.Lock()
	defer s.mu.Unlock()
	var buf strings.Builder
	buf.WriteString("httptest.Server blocked in Close after 5 seconds, waiting for connections:\n")
	for c, st := range s.conns {
		fmt.Fprintf(&buf, "  %T %p %v in state %v\n", c, c, c.RemoteAddr(), st)
	}
	log.Print(buf.String())
}

// CloseClientConnections关闭与测试服务器的所有打开的HTTP连接。
func (s *Server) CloseClientConnections() {
	s.mu.Lock()
	nconn := len(s.conns)
	ch := make(chan struct{}, nconn)
	for c := range s.conns {
		go s.closeConnChan(c, ch)
	}
	s.mu.Unlock()

	// 等待未完成的关闭完成。
	// 
	// 出于对迟到的妄想在Go 1.6中，我们限制了这可以等待多久，因为golang.org/issue/14291 
	// 还没有完全理解。至少这应该只用于
	// 在测试中。
	timer := time.NewTimer(5 * time.Second)
	defer timer.Stop()
	for i := 0; i < nconn; i++ {
		select {
		case <-ch:
		case <-timer.C:
			// 太慢了。放弃
			return
		}
	}
}

// 证书返回服务器使用的证书，如果
// 服务器不使用TLS，则返回零。
func (s *Server) Certificate() *x509.Certificate {
	return s.certificate
}

// 客户端返回一个配置为向服务器发出请求的HTTP客户端。
// 它被配置为信任服务器的TLS测试证书，并将
// 关闭其在服务器上的空闲连接。关闭。
func (s *Server) Client() *http.Client {
	return s.client
}

func (s *Server) goServe() {
	s.wg.Add(1)
	go func() {
		defer s.wg.Done()
		s.Config.Serve(s.Listener)
	}()
}

// wrap安装连接状态跟踪挂钩，以了解哪些
// 连接处于空闲状态。
func (s *Server) wrap() {
	oldHook := s.Config.ConnState
	s.Config.ConnState = func(c net.Conn, cs http.ConnState) {
		s.mu.Lock()
		defer s.mu.Unlock()

		// 在用户的连接状态钩子
		// （如果有）完成之前，请勿返回。如果不这样做，下面对forgetConn 
		// 的调用可能会在运行钩子之前将计数发送到0。
		s.wg.Add(1)
		defer s.wg.Done()

		switch cs {
		case http.StateNew:
			s.wg.Add(1)
			if _, exists := s.conns[c]; exists {
				panic("invalid state transition")
			}
			if s.conns == nil {
				s.conns = make(map[net.Conn]http.ConnState)
			}
			s.conns[c] = cs
			if s.closed {
				// 可能只是来自
				// 的套接字延迟绑定拨号，默认传输丢失了竞争（和
				// 因此此连接现在处于空闲状态，将永远不会使用）。
				s.closeConn(c)
			}
		case http.StateActive:
			if oldState, ok := s.conns[c]; ok {
				if oldState != http.StateNew && oldState != http.StateIdle {
					panic("invalid state transition")
				}
				s.conns[c] = cs
			}
		case http.StateIdle:
			if oldState, ok := s.conns[c]; ok {
				if oldState != http.StateActive {
					panic("invalid state transition")
				}
				s.conns[c] = cs
			}
			if s.closed {
				s.closeConn(c)
			}
		case http.StateHijacked, http.StateClosed:
			s.forgetConn(c)
		}
		if oldHook != nil {
			oldHook(c, cs)
		}
	}
}

// closeConn关闭c。
// s.mu必须持有。
func (s *Server) closeConn(c net.Conn) { s.closeConnChan(c, nil) }

// closeConnChan类似于closeConn，但当goroutine关闭c时，它使用可选通道接收值
// 。
func (s *Server) closeConnChan(c net.Conn, done chan<- struct{}) {
	c.Close()
	if done != nil {
		done <- struct{}{}
	}
}

// forgetConn从跟踪的CONN集合中删除c，并从
// waitgroup中递减，除非之前已删除。
// s.mu必须持有。
func (s *Server) forgetConn(c net.Conn) {
	if _, ok := s.conns[c]; ok {
		delete(s.conns, c)
		s.wg.Done()
	}
}
